﻿using BCCommon.Enums;
using BCDto.Chats;
using BCDto.Sim.Admin.Admin;
using BCDto.TMS.Driver;
using BCDto.Website.User;
using BCDto.WeChatComponent.WeChatUser;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;

namespace BCTMSRESTful.Configs
{
    /// <summary>
    /// Token扩展
    /// </summary>
    public static class TokenExtension
    {
#if DEBUG
        private static readonly string Issuer = "http://devapi.xablackcoffee.com";
        private static readonly string Audience = "http://devsim.xablackcoffee.com";
#else
        private static readonly string Issuer = "https://api.xablackcoffee.com";
        private static readonly string Audience = "https://sim.xablackcoffee.com";
#endif

        private static readonly SymmetricSecurityKey SecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Blackcoffee@xabc"));

        public static IServiceCollection AddJwtAuthentication(this IServiceCollection services)
        {
            if (services == null)
            {
                throw new ArgumentNullException("services");
            }

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(o =>
            {
                //不使用https
                o.RequireHttpsMetadata = false;
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    // The signing key must match!
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = SecurityKey,

                    // Validate the JWT Issuer (iss) claim
                    ValidateIssuer = true,
                    ValidIssuer = Issuer,

                    // Validate the JWT Audience (aud) claim
                    ValidateAudience = true,
                    ValidAudience = Audience,

                    // Validate the token expiry
                    ValidateLifetime = true,

                    // If you want to allow a certain amount of clock drift, set that here:
                    ClockSkew = TimeSpan.Zero
                };
                o.Events = new JwtBearerEvents
                {
                    //此处为权限验证失败后触发的事件
                    OnChallenge = async (context) =>
                    {
                        //此处代码为终止.Net Core默认的返回类型和数据结果，这个很重要哦，必须
                        context.HandleResponse();

                        //自定义自己想要返回的数据结果，我这里要返回的是Json对象，通过引用Newtonsoft.Json库进行转换
                        var payload = JsonConvert.SerializeObject(new { ErrorCode = "401", ErrorMessage = "很抱歉，您无权访问此接口。" });

                        //自定义返回的数据类型
                        context.Response.ContentType = "application/json";
                        context.Response.StatusCode = StatusCodes.Status401Unauthorized;

                        //输出Json数据结果
                        await context.Response.WriteAsync(payload);
                    }
                };
            });

            return services;
        }

        /// <summary>
        /// 解析Token
        /// </summary>
        /// <param name="token">token</param>
        /// <param name="role">角色。</param>
        /// <param name="ignoreValidTime">忽略有效期。</param>
        /// <returns></returns>
        public static long ResolveToken(string token, out RoleType role, bool ignoreValidTime = false)
        {
            var jwt = new JwtSecurityToken(token);

            if (!jwt.Audiences.Any(p => p == Audience))
            {
                throw new ArgumentException("Token 错误");
            }

            if (jwt.Issuer != Issuer)
            {
                throw new ArgumentException("Token 错误");
            }

            if (!Enum.TryParse(jwt.Claims.SingleOrDefault(p => p.Type.Equals(ClaimTypes.Role)).Value, out role))
            {
                throw new ArgumentException("Token 错误");
            }

            if (!ignoreValidTime)
            {
                if (jwt.ValidFrom > DateTime.Now || jwt.ValidTo < DateTime.Now)
                {
                    throw new ArgumentException("Token 已过期。");
                }
            }

            var claim = jwt.Claims.SingleOrDefault(p => p.Type.Equals(ClaimTypes.Name));

            if (!claim.Value.StartsWith(role.ToString()))
            {
                throw new ArgumentException("Token 错误。");
            }

            if (!long.TryParse(claim.Value.Substring(role.ToString().Length + 1), out var userId))
            {
                throw new ArgumentException("Token 错误。");
            }

            return userId;
        }

        /// <summary>
        /// 解析Token
        /// </summary>
        /// <param name="token">token</param>
        /// <param name="role">角色。</param>
        /// <returns></returns>
        public static IEnumerable<Claim> ResolveToken(string token, RoleType role)
        {
            var jwt = new JwtSecurityToken(token);

            if (!jwt.Audiences.Any(p => p == Audience))
            {
                throw new ArgumentException("Token错误");
            }

            if (jwt.Claims.SingleOrDefault(p => p.Value.Contains(role.ToString() + ":")) == default(Claim))
            {
                throw new ArgumentException("Token不是有效的");
            }

            if (jwt.Claims.SingleOrDefault(p => p.Type.Equals(ClaimTypes.Role)) == default(Claim))
            {
                throw new ArgumentException("Token不是有效的");
            }

            return jwt.Claims;
        }

        /// <summary>
        /// 生成访问令牌。
        /// </summary>
        /// <param name="user">用户信息。</param>
        /// <param name="expiration">有效期。</param>
        /// <param name="role">角色。</param>
        /// <param name="otherClaims"></param>
        /// <returns></returns>
        public static string GenerateToken(this UserDto user, TimeSpan expiration, RoleType role, params Claim[] otherClaims)
        {
            List<Claim> claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, role.ToString()+":" + user.UserId.ToString()),
                new Claim(ClaimTypes.Role, role.ToString()),
            };

            claims.AddRange(otherClaims);

            var now = DateTime.UtcNow;
            var signingCredentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);

            // Create the JWT and write it to a string
            var jwt = new JwtSecurityToken(Issuer, Audience, claims, now, now.Add(expiration), signingCredentials);

            return new JwtSecurityTokenHandler().WriteToken(jwt);
        }

        /// <summary>
        /// 生成访问令牌。
        /// </summary>
        /// <param name="admin">用户信息。</param>
        /// <param name="expiration">有效期。</param>
        /// <returns></returns>
        public static string GenerateToken(this AdminDto admin, TimeSpan expiration)
        {
            List<Claim> claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, "Admin:" + admin.AdminId.ToString()),
                new Claim(ClaimTypes.Role, RoleType.Admin.ToString())
            };

            var now = DateTime.UtcNow;
            var signingCredentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);

            // Create the JWT and write it to a string
            var jwt = new JwtSecurityToken(Issuer, Audience, claims, now, now.Add(expiration), signingCredentials);

            return new JwtSecurityTokenHandler().WriteToken(jwt);
        }

        /// <summary>
        /// 生成访问令牌。
        /// </summary>
        /// <param name="anonymous">用户信息。</param>
        /// <param name="expiration">有效期。</param>
        /// <returns></returns>
        public static string GenerateToken(this AnonymousDto anonymous, TimeSpan expiration)
        {
            List<Claim> claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, $"Anonymous:{anonymous.AnonymousId}"),
                new Claim(ClaimTypes.Role, RoleType.Anonymous.ToString())
            };

            var now = DateTime.UtcNow;
            var signingCredentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);

            // Create the JWT and write it to a string
            var jwt = new JwtSecurityToken(Issuer, Audience, claims, now, now.Add(expiration), signingCredentials);

            return new JwtSecurityTokenHandler().WriteToken(jwt);
        }
    }
}